package com.google.gwt.chart.client;

import com.google.gwt.widgetideas.graphics.client.*;
import com.google.gwt.dom.client.*;
import com.google.gwt.user.client.ui.*;
import java.util.ArrayList;
import com.google.gwt.user.client.Command;
import com.google.gwt.widgets.client.*;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.DOM;
import java.util.HashMap;

//=========================================================================
/**
 * Title:        PieChart <br>
 * Description:  $defaultDesc$<br>
 * Copyright:    Apache Public License 2.0<br>
 * Company:      $company$<br>
 * @author James Hutton
 */
//=========================================================================
public class PieChart
		extends Widget {
	private enum UPDATESTATE { BEGIN, DISABLED, END }
	//=========================================================================
	/**
	 * Title:        Wedge <br>
	 * Description:  $defaultDesc$<br>
	 * Copyright:    Apache Public License 2.0<br>
	 * Company:      $company$<br>
	 * @author James Hutton
	 */
	//=========================================================================
	public class Wedge {

		private Color color;
		private SpanLabel display;
		private String key;
		private int textoffset = 0;
		private double value;

		//=========================================================================
		/**
		 * Constructs a Wedge
		 */
		//=========================================================================
		public Wedge() {}

		//=========================================================================
		/**
		 * Constructs a Wedge
		 * @param key -
		 * @param val -
		 * @param color -
		 */
		//=========================================================================
		public Wedge(String key, double val, String color) {

			this();
			value = val;
			this.key = key;
			this.color = new Color(color);
			display = new SpanLabel(key);

		}

		//=========================================================================
		/**
		 * Method getColor
		 * @return Color
		 */
		//=========================================================================
		public Color getColor() {
			return color;
		}

		//=========================================================================
		/**
		 * Method getKey
		 * @return String
		 */
		//=========================================================================
		public String getKey() {
			return key;
		}

		//=========================================================================
		/**
		 * Method getLabel
		 * @return Label
		 */
		//=========================================================================
		public SpanLabel getLabel() {
			return display;
		}

		//=========================================================================
		/**
		 * Method getTextOffset
		 * @return int
		 */
		//=========================================================================
		public int getTextOffset() {
			return textoffset;
		}

		//=========================================================================
		/**
		 * Method getValue
		 * @return double
		 */
		//=========================================================================
		public double getValue() {
			return value;
		}

		//=========================================================================
		/**
		 * Method setColor
		 * @param color
		 */
		//=========================================================================
		public void setColor(Color color) {
			this.color = color;
		}

		//=========================================================================
		/**
		 * Method setTextOffset
		 * @param offset
		 */
		//=========================================================================
		public void setTextOffset(int offset) {
			textoffset = offset;
		}

		//=========================================================================
		/**
		 * Method setValue
		 * @param val
		 */
		//=========================================================================
		public void setValue(double val) {
			value = val;
		}

	}

	/** Field: DEFAULT_HEIGHT */
	public static final int DEFAULT_HEIGHT = 300;
	/** Field: DEFAULT_WIDTH */
	public static final int DEFAULT_WIDTH = 300;
	private GWTCanvas canvas;
	private Element div;
	private int radius;
	private Command redrawNow = new Command() {

		public void execute() {
			redraw();
		}

	};
	private double startRad = 0;
	private double total;
	private ArrayList<String> updatedList = new ArrayList<String>();

	private UPDATESTATE updatestate = UPDATESTATE.DISABLED;

	;
	private HashMap<String, Wedge> wedges = new HashMap<String, Wedge>();

	//=========================================================================
	/**
	 * Constructs a PieChart
	 */
	//=========================================================================
	public PieChart() {
		this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}

	//=========================================================================
	/**
	 * Constructs a PieChart
	 * @param width -
	 * @param height -
	 */
	//=========================================================================
	public PieChart(int width, int height) {

		canvas = new GWTCanvas(width, height);
		total = 0;
		radius = 100;
		div = DOM.createDiv();
		setElement(div);
		div.appendChild(canvas.getElement());
	}

	//=========================================================================
	/**
	 * Method addWedge
	 * @param w
	 */
	//=========================================================================
	public void addWedge(Wedge w) {
		addWedge(w, false);
	}

	//=========================================================================
	/**
	 * Method addWedge
	 * @param w
	 * @param update
	 */
	//=========================================================================
	public void addWedge(Wedge w, boolean update) {

		if (wedges.get(w.getKey()) != null) {
			total -= wedges.get(w.getKey()).getValue();
		}
		wedges.put(w.getKey(), w);
		total += w.getValue();
		if (updatestate == UPDATESTATE.BEGIN) {
			updatedList.add(w.getKey());
		}
		if (update) {
			redraw();
		}

	}

	//=========================================================================
	/**
	 * Method beginUpdate
	 */
	//=========================================================================
	public void beginUpdate() {

		if (updatestate != UPDATESTATE.END) {
			return;
		}
		updatestate = UPDATESTATE.BEGIN;
		updatedList.clear();

	}

	//=========================================================================
	/**
	 * Method clearWedges
	 */
	//=========================================================================
	public void clearWedges() {

		total = 0;
		wedges.clear();
		updatedList.clear();

	}

	//=========================================================================
	/**
	 * Method enableUpdateTracking
	 * @param enable
	 */
	//=========================================================================
	public void enableUpdateTracking(boolean enable) {

		if (enable && (updatestate == UPDATESTATE.DISABLED)) {
			updatestate = UPDATESTATE.END;
		}
		else if (updatestate != UPDATESTATE.DISABLED) {
			if (updatestate == UPDATESTATE.BEGIN) {
				endUpdate();
			}
			updatestate = UPDATESTATE.DISABLED;
		}

	}

	//=========================================================================
	/**
	 * Method endUpdate
	 */
	//=========================================================================
	public void endUpdate() {

		if (updatestate != UPDATESTATE.BEGIN) {
			return;
		}
		updatestate = UPDATESTATE.END;
		String[] keys = new String[wedges.size()];
		keys = wedges.keySet().toArray(keys);
		for (int i = 0; i < keys.length; i++) {
			String key = keys[i];
			if (!updatedList.contains(key)) {
				wedges.remove(key);
			}
		}


	}

	//=========================================================================
	/**
	 * Method getHeight
	 * @return int
	 */
	//=========================================================================
	public int getHeight() {

		String height = DOM.getStyleAttribute(canvas.getElement(), "height");
		if (height.endsWith("px")) {
			height = height.substring(0, height.length() - 2);
		}
		return Integer.parseInt(height);

	}

	//=========================================================================
	/**
	 * Method getRadius
	 * @return int
	 */
	//=========================================================================
	public int getRadius() {
		return radius;
	}

	//=========================================================================
	/**
	 * Method getWedge
	 * @param key
	 * @return Wedge
	 */
	//=========================================================================
	public Wedge getWedge(String key) {
		return wedges.get(key);
	}

	//=========================================================================
	/**
	 * Method getWidth
	 * @return int
	 */
	//=========================================================================
	public int getWidth() {

		String width = DOM.getStyleAttribute(canvas.getElement(), "width");
		if (width.endsWith("px")) {
			width = width.substring(0, width.length() - 2);
		}
		return Integer.parseInt(width);

	}

	//=========================================================================
	/**
	 * Method redraw
	 */
	//=========================================================================
	private void redraw() {

		if (updatestate == UPDATESTATE.BEGIN) {
			endUpdate();
		}
		canvas.clear();
		Element currElem = DOM.getFirstChild(getElement());
		Element nextElem;
		while (currElem != null) {
			nextElem = currElem.getNextSiblingElement();
			if (!currElem.equals(canvas.getElement())) {
				getElement().removeChild(currElem);
			}
			currElem = nextElem;
		}
		//if there are no wedges, display a message.
		if (wedges.size() == 0) {
			SpanLabel label = new SpanLabel("No Data Available");
			div.appendChild(label.getElement());
			label.getElement().getStyle().setProperty("position", "absolute");
			label.getElement().getStyle().setPropertyPx("left", getAbsoluteLeft() + getWidth() / 2 - label.getOffsetWidth() / 2);
			label.getElement().getStyle().setPropertyPx("top", getAbsoluteTop() + getHeight() / 2);
			return;
		}
		String[] keys = new String[wedges.size()];
		keys = wedges.keySet().toArray(keys);
		double currOffset = startRad;
		double centerx = ((double)getWidth()) / 2.0;
		double centery = ((double)getHeight()) / 2.0;
		for (int i = 0; i < keys.length; i++) {
			Wedge currWedge = wedges.get(keys[i]);
			canvas.beginPath();
			canvas.setFillStyle(currWedge.getColor());
			double width = (currWedge.getValue() / total) * (2 * Math.PI);
			canvas.arc(centerx, centery, radius, currOffset, width + currOffset, false);
			canvas.lineTo(centerx, centery);
			canvas.closePath();
			canvas.fill();
			SpanLabel label = currWedge.getLabel();

			Element elem = label.getElement();
			int textoffset = currWedge.getTextOffset();
			if (textoffset == 0) {
				textoffset = radius / 2;
			}
			div.appendChild(elem);
			double textxoffset = (Math.cos(currOffset + width / 2) * textoffset);
			double textyoffset = (Math.sin(currOffset + width / 2) * textoffset);
			elem.getStyle().setProperty("position", "absolute");
			elem.getStyle().setPropertyPx("left", (int)(getAbsoluteLeft() + centerx + textxoffset));
			elem.getStyle().setPropertyPx("top", (int)(getAbsoluteTop() + centery + textyoffset));
			elem.getStyle().setProperty("zIndex", "2");

			currOffset += width;

		}

	}

	//=========================================================================
	/**
	 * Method removeWedge
	 * @param key
	 * @return Wedge
	 */
	//=========================================================================
	public Wedge removeWedge(String key) {

		Wedge w = wedges.remove(key);
		if ((w != null) && (updatestate == UPDATESTATE.BEGIN)) {
			updatedList.remove(key);
		}
		return w;

	}

	//=========================================================================
	/**
	 * Method resize
	 * @param width
	 * @param height
	 */
	//=========================================================================
	public void resize(int width, int height) {
		canvas.resize(width, height);
	}

	//=========================================================================
	/**
	 * Method setRadius
	 * @param radius
	 */
	//=========================================================================
	public void setRadius(int radius) {
		this.radius = radius;
	}
	//=========================================================================
	/**
	 * Method update
	 */
	//=========================================================================
	public void update() {
		DeferredCommand.addCommand(redrawNow);
	}
}

